package org.aplikator.server.util;

import com.typesafe.config.Config;
import com.typesafe.config.ConfigFactory;

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.security.AccessController;
import java.security.PrivilegedActionException;
import java.security.PrivilegedExceptionAction;
import java.util.*;
import java.util.logging.Logger;

public class Configurator {

    public static final String STRUCTURE = "aplikator.structure";
    public static final String BUNDLE = "aplikator.bundle";
    public static final String HOME = "aplikator.home";
    public static final String CONFIG = "aplikator.config";
    public static final String DATASOURCE = "aplikator.datasourceName";
    public static final String BRAND = "aplikator.brand";
    public static final String DEFAULT_BUNDLE= "default_labels";

    private static final Logger LOG = Logger.getLogger(Configurator.class.getName());

    private static Configurator instance = instance();
    private Config config;
    private UTF8ClassLoader UTF8cl = new UTF8ClassLoader();
    private ResourceBundle.Control control = new UserControl();

    public static Configurator get() {
        return instance;
    }

    private static Configurator instance() {
        Configurator c = new Configurator();
        Config conf = ConfigFactory.load();
        Config userConf = ConfigFactory.parseFileAnySyntax(new File(conf.getString(CONFIG)));
        c.config = userConf.withFallback(conf);
        /*
         * System.out.println("APLIKATOR CONFIG: ");
         * System.out.println("property0:"+c.config.getString("property0"));
         * System.out.println("property1:"+c.config.getString("property1"));
         * System.out.println("property2:"+c.config.getString("property2"));
         * System.out.println("property3:"+c.config.getString("property3"));
         */
        return c;
    }

    public Config getConfig() {
        return config;
    }

    public String getLocalizedString(String key, Locale locale) {
        if (key == null)
            return "null";
        ResourceBundle rb = null;
        try{
            rb = ResourceBundle.getBundle(config.getString(BUNDLE), locale, UTF8cl, control);
        } catch (MissingResourceException ex) {
            LOG.warning("Cannot find resource bundle:"+config.getString(BUNDLE));
        }
        if (rb != null) {
            try {
                return rb.getString(key);
            } catch (MissingResourceException ex) {
                return key;
            }
        }

        return key;
    }

    /**
     * ResourceBundle Control implementation that loads resource bundle from the property file located in the directory
     * defined in HOME property (by default ${user.home}/.aplikator). The resource bundle has parent bundle loaded by
     * ControlWithDefault Control implementation
     */
    public class UserControl extends ResourceBundle.Control {
        private ResourceBundle.Control controlWithDefault = new ControlWithDefault();

        public final List<String> FORMATS = Collections.unmodifiableList(Arrays.asList("user"));

        public List<String> getFormats(String baseName) {
            if (baseName == null)
                throw new NullPointerException();
            return FORMATS;
        }

        public ResourceBundle newBundle(String baseName, Locale locale, String format, ClassLoader loader, boolean reload) throws IllegalAccessException, InstantiationException, IOException {
            if (baseName == null || locale == null || format == null || loader == null)
                throw new NullPointerException();
            ResourceBundle bundle = null;
            if (format.equals("user")) {
                ResourceBundle defaultBundle = controlWithDefault.newBundle(baseName, locale,"java.properties", loader, reload);
                String bundleName = toBundleName(baseName, locale);
                String resourceName = "file://"+config.getString(HOME) + "/" + bundleName + ".properties";
                InputStream stream = null;
                try{
                    URL url = new URL(resourceName);
                    if (url != null) {
                        URLConnection connection = url.openConnection();
                        if (connection != null) {
                            if (reload) {
                                // Disable caches to get fresh data for
                                // reloading.
                                connection.setUseCaches(false);
                            }
                            stream = connection.getInputStream();
                        }
                    }
                }catch(Throwable t){}
                if (stream != null) {
                    BufferedInputStream bis = new BufferedInputStream(stream);
                    bundle = new UserResourceBundle(UTF8ClassLoader.readUTFStreamToEscapedASCII(bis), defaultBundle);
                    bis.close();
                }else{
                    return defaultBundle;
                }
            }
            return bundle;
        }

    }

    /**
     * ResourceBundle Control implementation that loads resource bundle from the given property file, using the same
     * algortihm as default ResourceBundle.Control. The resource bundle has parent resource bundle loaded from the
     * property bundle DEFAULT_BUNDLE  (default_labels.properties)
     */
    public class ControlWithDefault extends ResourceBundle.Control {

        public final List<String> FORMATS = Collections.unmodifiableList(Arrays.asList("java.properties"));

        public List<String> getFormats(String baseName) {
            if (baseName == null)
                throw new NullPointerException();
            return FORMATS;
        }

        public ResourceBundle newBundle(String baseName, Locale locale, String format, final ClassLoader loader, final boolean reload) throws IllegalAccessException, InstantiationException, IOException {
            if (baseName == null || locale == null || format == null || loader == null)
                throw new NullPointerException();
            ResourceBundle bundle = null;
            if (format.equals("java.properties")) {
                ResourceBundle defaultBundle = super.newBundle(DEFAULT_BUNDLE, locale,"java.properties", loader, reload) ;
                String bundleName = toBundleName(baseName, locale);
                final String resourceName = toResourceName(bundleName, "properties");
                InputStream stream = null;
                try {
                    stream = AccessController.doPrivileged(
                            new PrivilegedExceptionAction<InputStream>() {
                                public InputStream run() throws IOException {
                                    InputStream is = null;
                                    if (reload) {
                                        URL url = loader.getResource(resourceName);
                                        if (url != null) {
                                            URLConnection connection = url.openConnection();
                                            if (connection != null) {
                                                // Disable caches to get fresh data for
                                                // reloading.
                                                connection.setUseCaches(false);
                                                is = connection.getInputStream();
                                            }
                                        }
                                    } else {
                                        is = loader.getResourceAsStream(resourceName);
                                    }
                                    return is;
                                }
                            });
                } catch (PrivilegedActionException e) {
                    throw (IOException) e.getException();
                }
                if (stream != null) {
                    BufferedInputStream bis = new BufferedInputStream(stream);
                    bundle = new UserResourceBundle(UTF8ClassLoader.readUTFStreamToEscapedASCII(bis), defaultBundle);
                    bis.close();
                }else{
                    return defaultBundle;
                }
            }
            return bundle;
        }

    }


    public static class UTF8ClassLoader extends ClassLoader {

        /**
         * Charset used when reading a properties file.
         */
        private static final String CHARSET = "UTF-8";

        /**
         * Buffer size used when reading a properties file.
         */
        private static final int BUFFER_SIZE = 2000;

        public UTF8ClassLoader() {
            super(UTF8ClassLoader.class.getClassLoader());
        }

        @Override
        public InputStream getResourceAsStream(String name) {
            try {
                return readUTFStreamToEscapedASCII(super.getResourceAsStream(name));
            } catch (IOException e) {
                e.printStackTrace();
                return null;
            }
        }

        // The following utility method is extracted from the Tapestry5 project
        // class org.apache.tapestry5.internal.services.MessagesSourceImpl
        //
        // Copyright 2006, 2007, 2008 The Apache Software Foundation
        //
        // Licensed under the Apache License, Version 2.0 (the "License");
        // you may not use this file except in compliance with the License.
        // You may obtain a copy of the License at
        //
        // http://www.apache.org/licenses/LICENSE-2.0
        //
        // Unless required by applicable law or agreed to in writing, software
        // distributed under the License is distributed on an "AS IS" BASIS,
        // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
        // implied.
        // See the License for the specific language governing permissions and
        // limitations under the License.

        /**
         * Reads a UTF-8 stream, performing a conversion to ASCII (i.e.,
         * ISO8859-1 encoding). Characters outside the normal range for
         * ISO8859-1 are converted to unicode escapes. In effect, it is
         * performing native2ascii on the files, on the fly.
         */
        private static InputStream readUTFStreamToEscapedASCII(InputStream is) throws IOException {
            Reader reader = new InputStreamReader(is, CHARSET);

            StringBuilder builder = new StringBuilder(BUFFER_SIZE);
            char[] buffer = new char[BUFFER_SIZE];

            while (true) {
                int length = reader.read(buffer);

                if (length < 0)
                    break;

                for (int i = 0; i < length; i++) {
                    char ch = buffer[i];

                    if (ch <= '\u007f') {
                        builder.append(ch);
                        continue;
                    }

                    builder.append(String.format("\\u%04x", (int) ch));
                }
            }

            reader.close();

            byte[] resourceContent = builder.toString().getBytes();

            return new ByteArrayInputStream(resourceContent);
        }

    }

    public class UserResourceBundle extends PropertyResourceBundle{

        public UserResourceBundle(InputStream stream, ResourceBundle parent) throws IOException {
            super(stream);
            setParent(parent);
        }

    }

}
